-
Notifications
You must be signed in to change notification settings - Fork 11
ot_i2c_transport
: implement device for external I2C communication
#190
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: ot-9.2.0
Are you sure you want to change the base?
Conversation
4aa1651
to
c990a45
Compare
c990a45
to
f759510
Compare
e89e6b2
to
d0289a0
Compare
d0289a0
to
d9c2532
Compare
0e7efaa
to
d716e69
Compare
d716e69
to
f3c8d36
Compare
I've made the wire protocol use raw bytes now instead of hex encoded ones, as I now have a functional implementation to talk to this in I have also made the device throttle processing using a timer to allow control to be returned to the vCPU so that it can observe the transfers taking place and prepare response bytes. I thought to do this manually in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is something I deeply do not understand with how the I2C stack is managed, but I do not want to delay this PR any further, as I guess the feature is needed.
I'll try to find some time to dig deeper.
nit pick: do not forget to update the format of the PR title |
ot_i2c_transport
: implement device for external I2C communication
I had a very quick look at the I2C target mode documentation, and I think the QEMUTimer is not required, as the target implements clock stretching. I think what is required is that the ACK/NACK (controller to target write) or payload (controller from target read) should only be send when the respective HW FIFO are in the proper state. If the FIFO is full (write) no ACK/NACK should be reported (and obviously not data should be send if the FIFO is empty (read). It looks like everything can be managed with the communication "transport" protocol, and I'm still not sure it covers all use cases and stick to the actual I2C implementation, such as:
If the remote host performs an illegal request (e.g. pushing a new byte whereas it has not received a ACK or NACK), the state machine should be moved to ERROR, and only restarted on a new connection for example. To sum up, I keep thinking it would help to draft some kind of chronogram to compare the I2C physical bus signalling vs. its translation into byte exchange over the comm. socket, which should cover all standard cases. |
This would be ideal, however clock stretching is unimplemented in I2C, as QEMU's I2C model is quite simplistic and I2C operations cannot be deferred until some data ready condition (e.g As a workaround, the timer is used to throttle the I/O to yield control back to the OT I2C, so that it can observe the transactions going on. Otherwise read transfers will all complete immediately on the I/O thread when it decides to process the I2C transfer, and at no point will OT I2C be aware a read transfer is taking place and have no opportunity to fill the TX FIFO.
This is already the case for transfers in both directions - once a read transfer is started, the controller issues a read by sending an At some point I would like to implement a better I2C layer to model this better, there are already some wrappers such as |
Yes you are right, the I2C API is far too limited, we will need to extend it at some point. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you open an issue for each open point so this PR can be merged but all the remaining issues are addressed? Please add the link to the comment before resolving it, so it's easier to track the resolution. Thanks.
|
||
## Protocol | ||
|
||
- The protocol over the chardev mirrors the I2C specification closely. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a discrepancy with the I2C protocol: when reading, the controller is supposed return a ACK/NACK to the slave to inform it whether it wants to keep reading or not.
Note that in I2C spec:
- the START bit is referred as
S
, while - the STOP bit is referred to as
P
I really keep thinking some kind of ASCII art would help understanding the CharDev based protocol vs. the physical protocol

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a discrepancy with the I2C protocol: when reading, the controller is supposed return a ACK/NACK to the slave to inform it whether it wants to keep reading or not.
Over the protocol, during a read transfer an R
byte is sent to read the next byte from the slave, or the transaction could be ended. This would roughly correspond to "ACK and read next byte" and "NACK and stop condition".
Note that in I2C spec:
* the START bit is referred as `S`, while * the STOP bit is referred to as `P`
I think I will rename them then.
Where `bus` is any of the system's I2C buses. On earlgrey for example, these are `ot-i2c0`, | ||
`ot-i2c1`, and `ot-i2c2`. | ||
|
||
## Protocol |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the protocol is missing a version info. It is really important since the CharDev peer implementation is likely to be part of another repository, which means no way to ensure the same protocol version is used on both sides.
There should be an initial handshake to ensure both peers can exchange knowledgeable data.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is a single byte containing the version before initiating every transaction sufficient?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it should be enough to perform it when a new connection is open (that is likely translating into another state in your FSM). When a connection is open, wait for some version info (with a prefix as for the other commands, maybe V
?) and a number. I do not think it is worth having some kind of major/minor version split.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are chardevs in QEMU guaranteed to have at most one active connection? Theoretically socket
type chardevs could have many active connections at once. Opentitan tooling will always use pty
type, but other users may use something else so it might be something to worry about.
If at most one active connection can exist, then I can do that with another state in the FSM.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, CharDev is either not connected or connected to a single client.
Note: on our side, I think we never use ptys, only unix and TCP sockets.
s->is_read_command = false; | ||
s->address = 0u; | ||
s->stall = false; | ||
s->stall_timer = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Timer is not cancelled on reset, this will bite hard at some point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is not that bad if we just use the timer (and not the additional stall
attribute) as I suggest in a separate comment, since the stall time is so short and we can then avoid timers causing state change across resets. But yes, maybe this should be documented as part of the lack of resets as discussed in other comments.
f3c8d36
to
1bbfd3e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Broadly I agree with previous comments on documenting the deficiencies of QEMU's I2C protocol, so that these issues are captured somewhere for reference in the future if more accurate I2C emulation is desired. I've also left a few separate comments.
hw/opentitan/ot_i2c_transport.c
Outdated
@@ -0,0 +1,318 @@ | |||
/* | |||
* OpenTitanTool I2C transport device for OpenTitan |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: this appears in many places, but I think it would be good if the QEMU model didn't reference OpenTitanTool/"transport"s at all. While it is what we use for testing upstream OpenTitan, it technically is just host-side SW responsible for interacting with OT and could be replaced with any working host SW stack.
If my understanding is correct, any host SW / user can interact with this device via the protocol, and so it doesn't make sense to specifically label "OpenTitanTool". Maybe "I2C host proxy device" or something similar would be more suitable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think "I2C host proxy device" or "I2C proxy device" could work as a name. "transport" was chosen as the immediate use-case for this was usage in opentitanlib
, so it made sense to me to call the device after that, but I think now the term is overloaded and confusing.
* THE SOFTWARE. | ||
*/ | ||
|
||
#ifndef HW_OPENTITAN_OT_I2C_TRANSPORT_H |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: as with above, maybe this might be better as something like HW_OT_HOST_I2C_PROXY_H
? etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the convention is to follow the exact file path, so that would be HW_OPENTITAN_OT_I2C_HOST_PROXY_H
for hw/opentitan/ot_i2c_host_proxy.h
(it's still an OpenTitan implementation, but not tied to OpenTitan tool if I get it right)
s->parser_state = CMD_I2C_START; | ||
break; | ||
} | ||
s->parser_state = CMD_I2C_ERR; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe a warning here? "malformed byte 0x%02x received in idle state"
OtI2CTransportState *s = OT_I2C_TRANSPORT(opaque); | ||
|
||
qemu_chr_fe_set_handlers(&s->chr, ot_i2c_transport_can_receive, | ||
ot_i2c_transport_receive, NULL, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this have an event callback to handle the cases where the CharDev is unexpectedly closed (event == CHR_EVENT_CLOSED
)? Maybe also at some points we should be polling qemu_chr_fe_backend_connected
and reset/error if appropriate. This might be better documented and left for a future PR however?
s->is_read_command = false; | ||
s->address = 0u; | ||
s->stall = false; | ||
s->stall_timer = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is not that bad if we just use the timer (and not the additional stall
attribute) as I suggest in a separate comment, since the stall time is so short and we can then avoid timers causing state change across resets. But yes, maybe this should be documented as part of the lack of resets as discussed in other comments.
through an externally driven `chardev` interface. | ||
|
||
The device can perform read or write transactions with bus devices and is intended to be used to | ||
drive the Opentitan I2C device Target mode functionality through `opentitanlib`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, maybe remove reference to opentitanlib
here as any host SW could use this protocol, not just upstream opentitan SW.
The intention of this device is to be an I2C host device that is driven externally by OpenTitanTool. It will be used to issue read and write operations to other I2C devices on its bus - of primary interest is issuing commands to the OpenTitan I2C host/target block. This "bus device" is present on the bus, but it cannot be interacted with by other bus devices and cannot have transactions targetting it. Signed-off-by: Alice Ziuziakowska <[email protected]>
Signed-off-by: Alice Ziuziakowska <[email protected]>
1bbfd3e
to
9d5ff98
Compare
Signed-off-by: Alice Ziuziakowska <[email protected]>
Needed for `ot_i2c_transport` to be able to communicate with `ot_i2c` in target mode. Signed-off-by: Alice Ziuziakowska <[email protected]>
9d5ff98
to
c76d1bb
Compare
This is an I2C device that allows external software such as
opentitanlib
to communicate with and issue commands to devices on a QEMU I2C bus. The communication is done through achardev
.See
docs/i2c_transport.md
for usage and protocol information.The device can be instantiated with these command line parameters:
-chardev pty,id=i2ctp -device ot-i2c-transport,bus=<bus>,chardev=i2ctp
, to instantiate on an I2C bus.See also lowRISC/opentitan#28450 for an implementation and test harness using this device.